#include "myelf.h"
void _out_buffer(char character, void *buffer, size_t idx, size_t maxlen);
void _out_null(char character, void *buffer, size_t idx, size_t maxlen);
int _vsnprintf(out_fct_type out, char *buffer, const size_t maxlen, const char *format, char *data, rodata_info_t *p_rodata_info);
/*below three function used to change big endian to little endian*/
static uint16_t swap16(uint16_t x)
{
    return (x >> 8) | (x << 8);
}

static uint32_t swap32(uint32_t x)
{
    return ((x >> 24) & 0x000000FF) |
           ((x >> 8) & 0x0000FF00) |
           ((x << 8) & 0x00FF0000) |
           ((x << 24) & 0xFF000000);
}

static uint64_t swap64(uint64_t x)
{
    return (x >> 56) |
           ((x & 0x00FF000000000000ull) >> 40) |
           ((x & 0x0000FF0000000000ull) >> 24) |
           ((x & 0x000000FF00000000ull) >> 8) |
           ((x & 0x00000000FF000000ull) << 8) |
           ((x & 0x0000000000FF0000ull) << 24) |
           ((x & 0x000000000000FF00ull) << 40) |
           (x << 56);
}
static order_type_e get_local_order()
{
    unsigned int x = 1;
    char *c = (char *)&x;
    if (*c)
    {
        return LITTLE_ORDER;
    }
    else
    {
        return BIG_ORDER;
    }
}

static int read_bytes_from_file(int fd, void *buf, int read_len)
{
    int real_len = read(fd, buf, read_len);
    if (real_len != read_len)
    {
        printf("read_bytes_from_file error\n");
        return FAILE;
    }
    return SUCCESS;
}

static int lseek_file_offset(int fd, off_t offset, int whence)
{
    off_t new_offset = lseek(fd, offset, whence);
    if (new_offset == (off_t)-1)
    {
        perror("lseek_file_offset error");
        close(fd);
        return FAILE;
    }
    return SUCCESS;
}

static void *malloc_warpper(uint32_t size)
{
    char *ptr = malloc(sizeof(char) * size);
    if (!ptr)
    {
        perror("malloc_warpper");
        abort();
    }
    memset(ptr, 0, size);
    return (void *)ptr;
}

static char *parser_elf_class32_shdr(int fd, Elf32_Shdr *elf32_shdr)
{
    char *shstrtab = malloc_warpper(elf32_shdr->sh_size + 1);
    lseek_file_offset(fd, elf32_shdr->sh_offset, SEEK_SET);
    read_bytes_from_file(fd, shstrtab, elf32_shdr->sh_size);
    shstrtab[elf32_shdr->sh_size] = '\0';
    return shstrtab;
}

char *parser_elf_class64_shdr(int fd, Elf64_Shdr *elf64_shdr)
{
    char *shstrtab = malloc_warpper(elf64_shdr->sh_size + 1);
    lseek_file_offset(fd, elf64_shdr->sh_offset, SEEK_SET);
    read_bytes_from_file(fd, shstrtab, elf64_shdr->sh_size);
    shstrtab[elf64_shdr->sh_size] = '\0';
    return shstrtab;
}

int open_elf_file(const char *filename)
{
    if (NULL == filename)
    {
        printf("error: filename is NULL\n");
        return FAILE;
    }
    int fd = open(filename, O_RDONLY);
    if (fd < 0)
    {
        printf("open file : %s error\n", filename);
        perror("open()");
        return FAILE;
    }
    return fd;
}
int read_elf_ident_header(int fd, elf_ident_t *ident)
{
    read_bytes_from_file(fd, ident, sizeof(elf_ident_t));
    if (ident->elfMagic[0] != 0x7f ||
        (ident->elfMagic[1] != 'E') ||
        (ident->elfMagic[2] != 'L') ||
        (ident->elfMagic[3] != 'F'))
    {
        printf("wrong file type\n");
        return FAILE;
    }
    return SUCCESS;
}

rodata_info32_t *parser_elf_class32_ehdr(int fd, Elf32_Ehdr *elf32_ehdr, int change_order_flag)
{
    lseek_file_offset(fd, -16, SEEK_CUR);
    read_bytes_from_file(fd, elf32_ehdr, sizeof(Elf32_Ehdr));
    if (change_order_flag)
    {
        elf32_ehdr->e_shoff = swap32(elf32_ehdr->e_shoff);
        elf32_ehdr->e_shentsize = swap16(elf32_ehdr->e_shentsize);
        elf32_ehdr->e_shnum = swap16(elf32_ehdr->e_shnum);
        elf32_ehdr->e_shstrndx = swap16(elf32_ehdr->e_shstrndx);
    }
    lseek_file_offset(fd, elf32_ehdr->e_shoff + elf32_ehdr->e_shstrndx * elf32_ehdr->e_shentsize, SEEK_SET);
    Elf32_Shdr shdr_strtab = {0};
    read_bytes_from_file(fd, &shdr_strtab, sizeof(Elf32_Shdr));
    if (change_order_flag)
    {
        shdr_strtab.sh_size = swap32(shdr_strtab.sh_size);
        shdr_strtab.sh_offset = swap32(shdr_strtab.sh_offset);
    }
    // shstrtab_need_free是所有段的名称的表
    char *shstrtab_need_free = parser_elf_class32_shdr(fd, &shdr_strtab);
    // 读取每一段，比对名称，找到.rodata段
    lseek_file_offset(fd, elf32_ehdr->e_shoff, SEEK_SET);
    Elf32_Shdr *shdr = malloc_warpper(elf32_ehdr->e_shnum * elf32_ehdr->e_shentsize + 1);
    read_bytes_from_file(fd, shdr, elf32_ehdr->e_shnum * elf32_ehdr->e_shentsize);
    Elf32_Shdr *rodata_shdr = NULL;
    for (int i = 0; i < elf32_ehdr->e_shnum; ++i)
    {
        if (change_order_flag)
        {
            shdr[i].sh_name = swap32(shdr[i].sh_name);
        }
        if (shdr[i].sh_name < shdr_strtab.sh_size && strncmp(&shstrtab_need_free[shdr[i].sh_name], ".rodata", 8) == 0)
        {
            rodata_shdr = &shdr[i];
            break;
        }
    }
    if (!rodata_shdr)
    {
        printf(".rodata section not found\n");
        free(shstrtab_need_free);
        free(shdr);
        close(fd);
        return NULL;
    }
    // 读取 .rodata 节的内容
    if (change_order_flag)
    {
        rodata_shdr->sh_size = swap32(rodata_shdr->sh_size);
        rodata_shdr->sh_offset = swap32(rodata_shdr->sh_offset);
        rodata_shdr->sh_addr = swap32(rodata_shdr->sh_addr);
    }
    char *rodata = malloc_warpper(rodata_shdr->sh_size + 1);
    lseek_file_offset(fd, rodata_shdr->sh_offset, SEEK_SET);
    read_bytes_from_file(fd, rodata, rodata_shdr->sh_size);
    rodata[rodata_shdr->sh_size] = '\0'; // 确保字符串以NULL结尾
    rodata_info32_t *rodata_info_p = (rodata_info32_t *)malloc_warpper(sizeof(rodata_info32_t));
    rodata_info_p->buf = rodata;
    rodata_info_p->size = rodata_shdr->sh_size;
    rodata_info_p->offset = rodata_shdr->sh_offset;
    rodata_info_p->addr = rodata_shdr->sh_addr;
    free(shstrtab_need_free);
    free(shdr);
    close(fd);
    return rodata_info_p;
}

rodata_info64_t *parser_elf_class64_ehdr(int fd, Elf64_Ehdr *elf64_ehdr, int change_order_flag)
{
    Elf64_Shdr elf64_section_header = {0};
    lseek_file_offset(fd, -16, SEEK_CUR);
    read_bytes_from_file(fd, elf64_ehdr, sizeof(Elf64_Ehdr));
    if (change_order_flag)
    {
        elf64_ehdr->e_shoff = swap64(elf64_ehdr->e_shoff);
        elf64_ehdr->e_shentsize = swap16(elf64_ehdr->e_shentsize);
        elf64_ehdr->e_shnum = swap16(elf64_ehdr->e_shnum);
        elf64_ehdr->e_shstrndx = swap16(elf64_ehdr->e_shstrndx);
    }
    lseek_file_offset(fd, elf64_ehdr->e_shoff + elf64_ehdr->e_shstrndx * elf64_ehdr->e_shentsize, SEEK_SET);
    read_bytes_from_file(fd, &elf64_section_header, sizeof(Elf64_Shdr));
    if (change_order_flag)
    {
        elf64_section_header.sh_size = swap64(elf64_section_header.sh_size);
        elf64_section_header.sh_offset = swap64(elf64_section_header.sh_offset);
    }
    // shstrtab_need_free是所有段的名称的表
    char *shstrtab_need_free = parser_elf_class64_shdr(fd, &elf64_section_header);
    // 读取每一段，比对名称，找到.rodata段
    lseek_file_offset(fd, elf64_ehdr->e_shoff, SEEK_SET);
    Elf64_Shdr *shdr = malloc_warpper(elf64_ehdr->e_shnum * elf64_ehdr->e_shentsize);
    read_bytes_from_file(fd, shdr, elf64_ehdr->e_shnum * elf64_ehdr->e_shentsize);
    // 找到 .rodata 节
    Elf64_Shdr *rodata_shdr = NULL;
    for (int i = 0; i < elf64_ehdr->e_shnum; ++i)
    {
        if (shdr[i].sh_name < elf64_section_header.sh_size && strncmp(&shstrtab_need_free[shdr[i].sh_name], ".rodata", 8) == 0)
        {
            rodata_shdr = &shdr[i];
            break;
        }
    }
    if (!rodata_shdr)
    {
        printf(".rodata section not found\n");
        free(shstrtab_need_free);
        free(shdr);
        close(fd);
        return NULL;
    }
    // 读取 .rodata 节的内容
    if (change_order_flag)
    {
        rodata_shdr->sh_size = swap64(rodata_shdr->sh_size);
        rodata_shdr->sh_offset = swap64(rodata_shdr->sh_offset);
        rodata_shdr->sh_addr = swap64(rodata_shdr->sh_addr);
    }
    char *rodata = malloc_warpper(rodata_shdr->sh_size + 1);
    lseek_file_offset(fd, rodata_shdr->sh_offset, SEEK_SET);
    read_bytes_from_file(fd, rodata, rodata_shdr->sh_size);
    rodata[rodata_shdr->sh_size] = '\0'; // 确保字符串以NULL结尾
    rodata_info64_t *rodata_info_p = (rodata_info64_t *)malloc_warpper(sizeof(rodata_info64_t));
    rodata_info_p->buf = rodata;
    rodata_info_p->size = rodata_shdr->sh_size;
    rodata_info_p->offset = rodata_shdr->sh_offset;
    rodata_info_p->addr = rodata_shdr->sh_addr;
    free(shstrtab_need_free);
    free(shdr);
    close(fd);
    return rodata_info_p;
}

rodata_info_t *parser_elf_file_enrty(const char *filename)
{
    elf_ident_t ident = {0};
    Elf32_Ehdr elf32_ehdr = {0};
    Elf64_Ehdr elf64_ehdr = {0};
    order_type_e local_order = get_local_order();
    rodata_info_t *rodata_info = (rodata_info_t *)malloc(sizeof(rodata_info_t));
    memset(rodata_info, 0, sizeof(rodata_info_t));
    int fd = open_elf_file(filename);
    read_elf_ident_header(fd, &ident);
    if (local_order != ident.dataOreder)
    {
        rodata_info->change_order = 1;
    }
    else
    {
        rodata_info->change_order = 0;
    }
    switch (ident.classType)
    {
    case ELFCLASS64:
    {
        rodata_info->rodata_64 = parser_elf_class64_ehdr(fd, &elf64_ehdr, rodata_info->change_order);
        rodata_info->class_type = 64;
        break;
    }
    case ELFCLASS32:
    {
        rodata_info->rodata_32 = parser_elf_class32_ehdr(fd, &elf32_ehdr, rodata_info->change_order);
        rodata_info->class_type = 32;
        break;
    }

    default:
    {
        printf("error file format , no 32 or 64 file system\n");
        free(rodata_info);
        rodata_info = NULL;
        break;
    }
    }
    return rodata_info;
}

rodata_info_t *load_elf_file(const char *filename)
{
    return parser_elf_file_enrty(filename);
}

void free_elf_resource(rodata_info_t *info_p)
{
    if (NULL != info_p)
    {
        if (32 == info_p->class_type)
        {
            free(info_p->rodata_32->buf);
            info_p->rodata_32->buf = NULL;
            free(info_p);
            info_p = NULL;
        }
        else if (64 == info_p->class_type)
        {
            free(info_p->rodata_64->buf);
            info_p->rodata_64->buf = NULL;
            free(info_p);
            info_p = NULL;
        }
    }
}

char *buf2str_addr32(rodata_info_t *rodata, uint32_t addr, uint32_t str_max_len)
{
    if (32 == rodata->class_type)
    {
        if ((uint32_t)addr < (uint32_t)(rodata->rodata_32->addr) ||
            ((uint32_t)addr > ((uint32_t)(rodata->rodata_32->addr) + rodata->rodata_32->size)))
        {
            printf("error offset\n");
            return NULL;
        }
        else
        {
            if (0 == str_max_len)
            {
                return &(rodata->rodata_32->buf[(uint32_t)addr - (uint32_t)(rodata->rodata_32->addr)]);
            }
            else
            {
                rodata->rodata_32->buf[(uint32_t)addr - (uint32_t)(rodata->rodata_32->addr) + str_max_len] = '\0';
                return &(rodata->rodata_32->buf[(uint32_t)addr - (uint32_t)(rodata->rodata_32->addr)]);
            }
        }
    }
    else
    {
        printf("error rodata info use,no class32 system\n");
        return NULL;
    }
}

char *buf2str_addr64(rodata_info_t *rodata, uint64_t addr, uint32_t str_max_len)
{
    if (64 == rodata->class_type)
    {
        if ((uint64_t)addr < (uint64_t)(rodata->rodata_64->addr) ||
            ((uint64_t)addr > ((uint64_t)(rodata->rodata_64->addr) + rodata->rodata_64->size)))
        {
            printf("error offset\n");
            return NULL;
        }
        else
        {
            if (0 == str_max_len)
            {
                return &(rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr)]);
            }
            else
            {
                rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr) + str_max_len] = '\0';
                return &(rodata->rodata_64->buf[(uint64_t)addr - (uint64_t)(rodata->rodata_64->addr)]);
            }
        }
    }
    else
    {
        printf("error rodata info use,no class64 system\n");
        return NULL;
    }
}

char *location_string_relocatio_offset(rodata_info_t *rodata, const char *pattern)
{
    const char *buf = NULL;
    size_t len = 0;
    if (64 == rodata->class_type)
    {
        buf = rodata->rodata_64->buf;
        len = rodata->rodata_64->size;
    }
    else
    {
        buf = rodata->rodata_32->buf;
        len = rodata->rodata_32->size;
    }
    const char *p = buf;
    const char *end = buf + len;
    size_t pattern_len = strlen(pattern);

    while (p <= end - pattern_len)
    {
        if (strncmp(p, pattern, pattern_len) == 0)
        {
            if (p + pattern_len == end || strncmp(p + pattern_len, pattern, 1) != 0)
            {
                return p;
            }
        }
        p++;
    }
    return NULL;
}

char *element2char_p(char **p_data, rodata_info_t *p_rodata_info)
{
    char *p_str_format = NULL;
    char *str_format_addr = NULL;
    uint32_t offset = 0;
    uint32_t size = 0;
    char *buf = NULL;
    str_format_addr = *p_data;
    if (32 == p_rodata_info->class_type)
    {
        str_format_addr -= (p_rodata_info->rodata_32->addr);
        offset = (p_rodata_info->rodata_32->offset);
        size = (p_rodata_info->rodata_32->size);
        buf = (p_rodata_info->rodata_32->buf);
    }
    else
    {
        str_format_addr -= (p_rodata_info->rodata_64->addr);
        offset = (p_rodata_info->rodata_64->offset);
        size = (p_rodata_info->rodata_64->size);
        buf = (p_rodata_info->rodata_64->buf);
    }

    if ((uint32_t)(str_format_addr) < (offset + size))
    {
        p_str_format = (buf) + (uint32_t)(str_format_addr);
        *p_data += sizeof(char *);
    }
    else
    {
        p_str_format = *p_data;
        *p_data += strlen(p_str_format) + 1;
    }
    return p_str_format;
}

int buf2str_bitStream(rodata_info_t *p_rodata_info, char *buf_out, int out_len, char *buf_in, int in_len)
{
    int offset = 0;
    char *p_data = buf_in;
    char *p_str_format = NULL;
    buff_header_t *buff_header = NULL;
    int buf_out_len = 0;
    for (; offset < in_len;)
    {
        p_data = buf_in + offset;
        buff_header = (buff_header_t *)(p_data);
        p_data += sizeof(buff_header_t);

        // p_str_format = buf2str_addr(p_rodata_info, p_data, 0);
        p_str_format = element2char_p(&p_data, p_rodata_info);
        if (NULL != p_str_format)
        {
            memcpy(buf_out, p_str_format, strlen(p_str_format));
            buf_out_len += _vsnprintf(_out_buffer, buf_out + buf_out_len, out_len - buf_out_len, p_str_format, p_data, p_rodata_info);
            offset += buff_header->len;
        }
        else
        {
            return 0;
        }
    }

    return buf_out_len;
}

bool _is_digit(char ch)
{
    return (ch >= '0') && (ch <= '9');
}

void _out_buffer(char character, void *buffer, size_t idx, size_t maxlen)
{
    if (idx < maxlen)
    {
        ((char *)buffer)[idx] = character;
    }
}

void _out_null(char character, void *buffer, size_t idx, size_t maxlen)
{
    (void)character;
    (void)buffer;
    (void)idx;
    (void)maxlen;
}

unsigned int _atoi(const char **str)
{
    unsigned int i = 0U;
    while (_is_digit(**str))
    {
        i = i * 10U + (unsigned int)(*((*str)++) - '0');
    }
    return i;
}

int element2int(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(int);

    return *(int *)p_val;
}

long element2long(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(long);

    return *(long *)p_val;
}

void *element2char(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(char);

    return *(char *)p_val;
}
short element2short(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(short);

    return *(short *)p_val;
}

unsigned long element2unsigned_long(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(unsigned long);

    return *(unsigned long *)p_val;
}

unsigned short element2unsigned_short(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(unsigned short);

    return *(unsigned short *)p_val;
}
unsigned int element2unsigned_int(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(unsigned int);

    return *(unsigned int *)p_val;
}

unsigned int _strnlen_s(const char *str, size_t maxsize)
{
    const char *s;
    for (s = str; *s && maxsize--; ++s)
        ;
    return (unsigned int)(s - str);
}

void *element2void_p(char **p_data)
{
    char *p_val = NULL;
    p_val = *p_data;
    *p_data += sizeof(void *);

    return *(void **)p_val;
}
size_t _out_rev(out_fct_type out, char *buffer, size_t idx, size_t maxlen, const char *buf, size_t len, unsigned int width, unsigned int flags)
{
    const size_t start_idx = idx;
    size_t i = 0;
    // pad spaces up to given width
    if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD))
    {
        for (i = len; i < width; i++)
        {
            out(' ', buffer, idx++, maxlen);
        }
    }

    // reverse string
    while (len)
    {
        out(buf[--len], buffer, idx++, maxlen);
    }

    // append pad spaces up to given width
    if (flags & FLAGS_LEFT)
    {
        while (idx - start_idx < width)
        {
            out(' ', buffer, idx++, maxlen);
        }
    }

    return idx;
}

// internal itoa format
size_t _ntoa_format(out_fct_type out, char *buffer, size_t idx, size_t maxlen, char *buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
{
    // pad leading zeros
    if (!(flags & FLAGS_LEFT))
    {
        if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE))))
        {
            width--;
        }
        while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE))
        {
            buf[len++] = '0';
        }
        while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE))
        {
            buf[len++] = '0';
        }
    }

    // handle hash
    if (flags & FLAGS_HASH)
    {
        if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width)))
        {
            len--;
            if (len && (base == 16U))
            {
                len--;
            }
        }
        if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE))
        {
            buf[len++] = 'x';
        }
        else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE))
        {
            buf[len++] = 'X';
        }
        else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE))
        {
            buf[len++] = 'b';
        }
        if (len < PRINTF_NTOA_BUFFER_SIZE)
        {
            buf[len++] = '0';
        }
    }

    if (len < PRINTF_NTOA_BUFFER_SIZE)
    {
        if (negative)
        {
            buf[len++] = '-';
        }
        else if (flags & FLAGS_PLUS)
        {
            buf[len++] = '+'; // ignore the space if the '+' exists
        }
        else if (flags & FLAGS_SPACE)
        {
            buf[len++] = ' ';
        }
    }

    return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}

size_t _ntoa_long(out_fct_type out, char *buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
{
    char buf[PRINTF_NTOA_BUFFER_SIZE];
    size_t len = 0U;

    // no hash for 0 values
    if (!value)
    {
        flags &= ~FLAGS_HASH;
    }

    // write if precision != 0 and value is != 0
    if (!(flags & FLAGS_PRECISION) || value)
    {
        do
        {
            const char digit = (char)(value % base);
            buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
            value /= base;
        } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
    }

    return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}

int _vsnprintf(out_fct_type out, char *buffer, const size_t maxlen, const char *format, char *data, rodata_info_t *p_rodata_info)
{
    unsigned int flags, width, precision, n;
    size_t idx = 0U;

    if (!buffer)
    {
        // use null output function
        out = _out_null;
    }

    while (*format)
    {
        // format specifier?  %[flags][width][.precision][length]
        if (*format != '%')
        {
            // no
            out(*format, buffer, idx++, maxlen);
            format++;
            continue;
        }
        else
        {
            // yes, evaluate it
            format++;
        }

        // evaluate flags
        flags = 0U;
        do
        {
            switch (*format)
            {
            case '0':
                flags |= FLAGS_ZEROPAD;
                format++;
                n = 1U;
                break;
            case '-':
                flags |= FLAGS_LEFT;
                format++;
                n = 1U;
                break;
            case '+':
                flags |= FLAGS_PLUS;
                format++;
                n = 1U;
                break;
            case ' ':
                flags |= FLAGS_SPACE;
                format++;
                n = 1U;
                break;
            case '#':
                flags |= FLAGS_HASH;
                format++;
                n = 1U;
                break;
            default:
                n = 0U;
                break;
            }
        } while (n);

        // evaluate width field
        width = 0U;
        if (_is_digit(*format))
        {
            width = _atoi(&format);
        }
        else if (*format == '*')
        {
            const int w = element2int(&data);
            if (w < 0)
            {
                flags |= FLAGS_LEFT; // reverse padding
                width = (unsigned int)-w;
            }
            else
            {
                width = (unsigned int)w;
            }
            format++;
        }

        // evaluate precision field
        precision = 0U;
        if (*format == '.')
        {
            flags |= FLAGS_PRECISION;
            format++;
            if (_is_digit(*format))
            {
                precision = _atoi(&format);
            }
            else if (*format == '*')
            {
                const int prec = (int)element2int(&data);
                precision = prec > 0 ? (unsigned int)prec : 0U;
                format++;
            }
        }

        // evaluate length field
        switch (*format)
        {
        case 'l':
            flags |= FLAGS_LONG;
            format++;
            if (*format == 'l')
            {
                flags |= FLAGS_LONG_LONG;
                format++;
            }
            break;
        case 'h':
            flags |= FLAGS_SHORT;
            format++;
            if (*format == 'h')
            {
                flags |= FLAGS_CHAR;
                format++;
            }
            break;
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
        case 't':
            flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            format++;
            break;
#endif
        case 'j':
            flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            format++;
            break;
        case 'z':
            flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
            format++;
            break;
        default:
            break;
        }

        // evaluate specifier
        switch (*format)
        {
        case 'd':
        case 'i':
        case 'u':
        case 'x':
        case 'X':
        case 'o':
        case 'b':
        {
            // set the base
            unsigned int base;
            if (*format == 'x' || *format == 'X')
            {
                base = 16U;
            }
            else if (*format == 'o')
            {
                base = 8U;
            }
            else if (*format == 'b')
            {
                base = 2U;
            }
            else
            {
                base = 10U;
                flags &= ~FLAGS_HASH; // no hash for dec format
            }
            // uppercase
            if (*format == 'X')
            {
                flags |= FLAGS_UPPERCASE;
            }

            // no plus or space flag for u, x, X, o, b
            if ((*format != 'i') && (*format != 'd'))
            {
                flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
            }

            // ignore '0' flag when precision is given
            if (flags & FLAGS_PRECISION)
            {
                flags &= ~FLAGS_ZEROPAD;
            }

            // convert the integer
            if ((*format == 'i') || (*format == 'd'))
            {
                // signed
                if (flags & FLAGS_LONG_LONG)
                {
#if defined(PRINTF_SUPPORT_LONG_LONG)
                    const long long value = element2longlong(&data);
                    idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
#endif
                }
                else if (flags & FLAGS_LONG)
                {
                    const long value = element2long(&data);
                    idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
                }
                else
                {
                    const int value = (flags & FLAGS_CHAR) ? (char)element2char(&data) : (flags & FLAGS_SHORT) ? (short int)element2short(&data)
                                                                                                               : element2int(&data);
                    idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
                }
            }
            else
            {
                // unsigned
                if (flags & FLAGS_LONG_LONG)
                {
#if defined(PRINTF_SUPPORT_LONG_LONG)
                    idx = _ntoa_long_long(out, buffer, idx, maxlen, element2unsigned_longlong(&data), false, base, precision, width, flags);
#endif
                }
                else if (flags & FLAGS_LONG)
                {
                    idx = _ntoa_long(out, buffer, idx, maxlen, element2unsigned_long(&data), false, base, precision, width, flags);
                }
                else
                {
                    const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)element2char(&data) : (flags & FLAGS_SHORT) ? (unsigned short int)element2unsigned_short(&data)
                                                                                                                                 : element2unsigned_int(&data);
                    idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
                }
            }
            format++;
            break;
        }
#if defined(PRINTF_SUPPORT_FLOAT)
        case 'f':
        case 'F':
            if (*format == 'F')
                flags |= FLAGS_UPPERCASE;
            idx = _ftoa(out, buffer, idx, maxlen, element2double(&data), precision, width, flags);
            format++;
            break;
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
        case 'e':
        case 'E':
        case 'g':
        case 'G':
            if ((*format == 'g') || (*format == 'G'))
                flags |= FLAGS_ADAPT_EXP;
            if ((*format == 'E') || (*format == 'G'))
                flags |= FLAGS_UPPERCASE;
            idx = _etoa(out, buffer, idx, maxlen, element2double(&data), precision, width, flags);
            format++;
            break;
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
        case 'c':
        {
            unsigned int l = 1U;
            // pre padding
            if (!(flags & FLAGS_LEFT))
            {
                while (l++ < width)
                {
                    out(' ', buffer, idx++, maxlen);
                }
            }
            // char output
            out((char)element2char(&data), buffer, idx++, maxlen);
            // post padding
            if (flags & FLAGS_LEFT)
            {
                while (l++ < width)
                {
                    out(' ', buffer, idx++, maxlen);
                }
            }
            format++;
            break;
        }

        case 's':
        {
            const char *p = element2char_p(&data, p_rodata_info);
            unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
            if (flags & FLAGS_PRECISION)
            {
                l = (l < precision ? l : precision);
            }
            if (!(flags & FLAGS_LEFT))
            {
                while (l++ < width)
                {
                    out(' ', buffer, idx++, maxlen);
                }
            }
            while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--))
            {
                out(*(p++), buffer, idx++, maxlen);
            }
            if (flags & FLAGS_LEFT)
            {
                while (l++ < width)
                {
                    out(' ', buffer, idx++, maxlen);
                }
            }
            format++;
            break;
        }

        case 'p':
        {
            width = sizeof(void *) * 2U;
            flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
            const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
            if (is_ll)
            {
                idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)element2void_p(&data), false, 16U, precision, width, flags);
            }
            else
            {
#endif
                idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)element2void_p(&data)), false, 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
            }
#endif
            format++;
            break;
        }

        case '%':
            out('%', buffer, idx++, maxlen);
            format++;
            break;

        default:
            out(*format, buffer, idx++, maxlen);
            format++;
            break;
        }
    }

    // termination
    out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);

    // return written chars without terminating \0
    return (int)idx;
}
